package jp.sourceforge.akjrcp.ffmpeg;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class FFMpegPreference {
	public static Log log = LogFactory.getLog(FFMpegPreference.class);

	private List<String> mapList = new ArrayList<String>();

	private int generation = FFMpegPreference.GENERATION1;

	private String ffmpegPath;

	private String audioCodec;

	private int audioBitrate;

	private int audioRate;

	private int audioChannel;

	private String videoCodec;

	private double videoRate;

	private int videoBitrate;

	private int videoMinBitrate;

	private int videoMaxBitrate;

	private String videoSize;

	private String videoAspect;

	private String target;

	private String format;

	private boolean enableVideo = true;

	private boolean enableAudio = true;

	private boolean samequality;

	public static final String OPTION_INPUT = "-i";

	public static final String OPTION_OVERWRITE = "-y";

	public static final String OPTION_AUDIO_CODEC = "-acodec";

	public static final String OPTION_AUDIO_RATE = "-ar";

	public static final String OPTION_AUDIO_BITRATE = "-ab";

	public static final String OPTION_AUDIO_CHANNEL = "-ac";

	public static final String OPTION_DISABLE_AUDIO = "-an";

	public static final String OPTION_DISABLE_VIDEO = "-vn";

	public static final String OPTION_VIDEO_CODEC = "-vcodec";

	public static final String OPTION_VIDEO_RATE = "-r";

	public static final String OPTION_VIDEO_BITRATE = "-b";

	public static final String OPTION_VIDEO_MIN_BITRATE = "-minrate"; // for
																		// cbr

	public static final String OPTION_VIDEO_MAX_BITRATE = "-maxrate"; // for
																		// cbr

	public static final String OPTION_VIDEO_BUFFER_SIZE = "-bufsize";

	public static final String OPTION_VIDEO_SIZE = "-s";

	public static final String OPTION_VIDEO_ASPECT = "-aspect";

	public static final String OPTION_FORMAT = "-f";

	public static final String OPTION_TARGET = "-target";

	public static final String OPTION_QMAX = "-qmax";

	public static final String OPTION_QMIN = "-qmin";

	public static final String OPTION_MBD = "-mbd";

	public static final String OPTION_FLAGS = "-flags";

	public static final String OPTION_CODER = "-coder";

	public static final String OPTION_LEVEL = "-level";

	private int level;

	private int coder;

	private String vtag;

	public static final String OPTION_VTAG = "-vtag";

	private String extension;

	private boolean forceOverwrite = true;

	private boolean twoPassEncode = false;

	private int guessSize = 1024;// dummy guess;

	private boolean highQuality;

	private int qmin;

	private int qmax;

	private static final String OPTION_VIDEO_SAME_QUALITY = "-sameq";

	private String flags[];

	private int mbd;// 1 or 2 for good quality

	private int maxRate;

	private int bufSize;

	private int bt;

	public static final String OPTION_BT = "-bt";

	public static final String OPTION_MAXRATE = "-maxrate";

	public static final String OPTION_BUFSIZE = "-bufsize";

	private boolean loop; // for image loop

	private boolean shortest; // length is shorter input

	public static final String OPTION_LOOP = "-loop";

	public static final String OPTION_LOOP_INPUT = "-loop_input";

	public static final String OPTION_SHORTEST = "-shortest";

	public static final String OPTION_THREADS = "-threads";

	private int threads;

	public static final String OPTION_MAP = "-map";

	private boolean deinterlace;

	public static final int GENERATION2 = 2; // after 9260

	public static final int GENERATION1 = 1; // 9017 & 9133

	//ffmpeg versions
	public static final int GENERATION0 = 0;

	public static final String OPTION_DEINTERLACE = "-deinterlace";

	public int getThreads() {
		return threads;
	}

	public void setThreads(int threads) {
		this.threads = threads;
	}

	/**
	 * @deprecated
	 * 
	 */
	public boolean isLoop() {
		return loop;
	}

	/**
	 * @deprecated
	 * 
	 */
	public void setLoop(boolean loop) {
		this.loop = loop;
	}

	public boolean isShortest() {
		return shortest;
	}

	public void setShortest(boolean shortest) {
		this.shortest = shortest;
	}

	public int getMbd() {
		return mbd;
	}

	public void setMbd(int mbd) {
		this.mbd = mbd;
	}

	public String[] getFlags() {
		return flags;
	}

	public void setFlags(String[] flags) {
		this.flags = flags;
	}

	public int getGuessSize() {
		return guessSize;
	}

	public void setGuessSize(int guessSize) {
		// log.infoset-guess:"+guessSize);
		this.guessSize = guessSize;
	}

	public boolean isTwoPassEncode() {
		return twoPassEncode;
	}

	public void setTwoPassEncode(boolean twoPassEncode) {
		this.twoPassEncode = twoPassEncode;
	}

	public String getExtension() {
		return extension;
	}

	public void setExtension(String extension) {
		this.extension = extension;
	}

	public static boolean isTwopassNeedAudio(String format){
		if(format==null){
			return false;
		}
		String[] needs={FFMpegOptions.FORMAT_PSP};
		for (int i = 0; i < needs.length; i++) {
			if(format.equals(needs[i])){
				return true;
			}
		}
		return false;
	}
	public String[] toCommandPass1(FFMpegInput inputs[],
			String passlog, String output) {
		String tmp[] = toCommand(inputs, output);
		List commandList = new ArrayList();
		// String ret[]=new String[tmp.length+4];
		for (int i = 0; i < tmp.length - 1; i++) {// final is output
			commandList.add(tmp[i]);
		}
		// hig hquality special settings.
		if (highQuality) {
			addHighQualityOption(commandList);
		}
		
		if(isTwopassNeedAudio(format)){
		//	
		}else{
		commandList.add("-an"); // no audio
		}
		
		commandList.add("-pass");
		commandList.add("1");
		commandList.add("-passlogfile");
		commandList.add(passlog);
		commandList.add(output);
		return (String[]) commandList.toArray(new String[0]);
	}

	/**
	 * @deprecated
	 */
	private void addHighQualityOption(List commandList) {
		// special mpeg2
		commandList.add("-g");
		commandList.add("18");
		commandList.add("-mbd");
		commandList.add("rd");

		commandList.add("-cmp");
		commandList.add("2");

		commandList.add("-subcmp");
		commandList.add("2");

		commandList.add("-flags");
		commandList.add("trell");

		// commandList.add("-qns");
		// commandList.add("2");



		commandList.add("-bf");
		commandList.add("2");

		/*
		 * //still testing.
		 * 
		 * 
		 * 
		 * //commandList.add("-cmp");//9017 2 is problem //commandList.add("0");
		 * //commandList.add("-subcmp"); //commandList.add("0");
		 * commandList.add("-flags"); commandList.add("trell");
		 * commandList.add("-me_method"); commandList.add("umh");
		 * //commandList.add("-qmin"); //commandList.add("1");
		 * //commandList.add("-qmax"); //commandList.add("4");
		 */
		/*
		 * commandList.add("-refs"); commandList.add(""+3);
		 * 
		 * 
		 * 
		 * 
		 * commandList.add("-subq"); commandList.add(""+6);
		 * 
		 * commandList.add("-brdo"); commandList.add(""+1);
		 * 
		 * commandList.add("-me_range"); commandList.add(""+21);
		 * 
		 * commandList.add("-chroma"); commandList.add(""+1);
		 * 
		 * commandList.add("-slice"); commandList.add(""+2);
		 * 
		 * 
		 * 
		 * commandList.add("-keyint_min"); commandList.add(""+25);
		 * 
		 * commandList.add("-sc_threshold"); commandList.add(""+40);
		 * 
		 */

		// commandList.add("-qcomp");
		// commandList.add(""+0.60);

	}

	public String[] toCommandPass2(FFMpegInput[] inputs,
			String passlog, String output) {
		String tmp[] = toCommand(inputs, output);
		List<String> commandList = new ArrayList<String>();
		// String ret[]=new String[tmp.length+4];
		for (int i = 0; i < tmp.length - 1; i++) {// final is output
			commandList.add(tmp[i]);
		}
		// hig hquality special settings.
		if (highQuality) {
			addHighQualityOption(commandList);
		}
		commandList.add("-pass");
		commandList.add("2");
		commandList.add("-passlogfile");
		commandList.add(passlog);
		commandList.add(output);
		return (String[]) commandList.toArray(new String[0]);
	}

	/**
	 * 
	 * use Input
	 * 
	 * @param input
	 * @param avsPath
	 * @param output
	 * @return
	 */
	public String[] toCommand(String input, String avsPath, String output) {
		// log.info(input+","+avsPath+","+output);
		if (input == null || output == null) {
			// should exception
			return null;
		}
		String inputs[];
		if (avsPath == null) {

			inputs = new String[] { input };
		} else {
			inputs = new String[] { avsPath };
		}

		return toCommand(inputs, output);
	}

	// support multi input
	// actually this type don't support input -f option or others,this is
	// temporaly work
	public String[] toCommand(String input[], String output) {
		FFMpegInput[] ffmpegInputs = new FFMpegInput[input.length];
		for (int i = 0; i < ffmpegInputs.length; i++) {
			ffmpegInputs[i] = new FFMpegInput(input[i]);

		}
		return toCommand(ffmpegInputs, output);

	}

	public static String toSuiteVideoCodec(String vcodec, int g) {
		String codec = vcodec;
		if (g == 2) {
			if (vcodec.equals(FFMpegOptions.CODEC_VIDEO_XVID)) {
				codec = FFMpegOptions.CODEC_VIDEO_LIB_XVID;
			} else if (vcodec.equals(FFMpegOptions.CODEC_VIDEO_H264)) {
				codec = FFMpegOptions.CODEC_VIDEO_LIB_X264;// to future.
			}
			// should i support mp3lame?
		}

		return codec;
	}

	public static String toSuiteAudioCodec(String acodec, int g) {
		String codec = acodec;
		if (g == 2) {
			if (acodec.equals(FFMpegOptions.CODEC_AUDIO_AAC)) {
				codec = FFMpegOptions.CODEC_AUDIO_LIB_FAAC;
			} else if (acodec.equals(FFMpegOptions.CODEC_AUDIO_MP3)) {
				codec = FFMpegOptions.CODEC_AUDIO_LIB_MP3LAME;
			}
			// should i support mp3lame?
		}
		log.trace(acodec + " g=" + g + " new=" + codec);
		return codec;
	}

	public String[] toCommand(FFMpegInput inputs[], String output) {
		List<String> params = new ArrayList<String>();
		if (ffmpegPath == null) {
			params.add("ffmpeg");
		} else {
			params.add(ffmpegPath);
		}

		addFirstOptions(params);

		if (forceOverwrite) {
			params.add(OPTION_OVERWRITE);
		}
		for (int i = 0; i < inputs.length; i++) {
			if (inputs[i].isLoop()) {
				params.add(OPTION_LOOP_INPUT);
				// params.add("1");// didn't work
			}
			if (inputs[i].getFormat() != null) {
				params.add(OPTION_FORMAT);
				params.add(inputs[i].getFormat());
			}
			params.add(OPTION_INPUT);
			params.add(inputs[i].getPath());
			
			if(inputs[i].getMap()!=null){
				params.add(OPTION_MAP);
				params.add(inputs[i].getMap());
			}

		}

		if (target != null) {
			params.add(OPTION_TARGET);
			params.add(FFMpegOptions.TARGET_NTSC_DVD);
		}

		if (format != null) {
			params.add(OPTION_FORMAT);
			params.add(format);
		}

		if (enableAudio) {
			if (valid(audioCodec)) {
				params.add(OPTION_AUDIO_CODEC);
				params.add(toSuiteAudioCodec(audioCodec, getGeneration()));
			}

			if (audioRate != 0) {
				params.add(OPTION_AUDIO_RATE);
				params.add("" + audioRate);
			}

			if (audioBitrate != 0) {
				params.add(OPTION_AUDIO_BITRATE);
				params.add("" + audioBitrate + "k");
			}

			if (audioChannel != 0) {
				params.add(OPTION_AUDIO_CHANNEL);
				params.add("" + audioChannel);
			}
		} else {
			params.add(OPTION_DISABLE_AUDIO);
		}

		if (enableVideo) {
			if (valid(videoCodec)) {
				params.add(OPTION_VIDEO_CODEC);
				params.add(toSuiteVideoCodec(videoCodec, getGeneration()));
			}

			if (videoRate != 0) {
				params.add(OPTION_VIDEO_RATE);
				params.add("" + videoRate);
			}

			if (videoBitrate != 0) {
				params.add(OPTION_VIDEO_BITRATE);
				params.add("" + videoBitrate + "k");
			}
			if (videoMaxBitrate != 0) {
				params.add(OPTION_VIDEO_MAX_BITRATE);
				params.add("" + videoMaxBitrate + "k");
				params.add(OPTION_VIDEO_BUFFER_SIZE);
				params.add("" + (videoMaxBitrate + 2) + "k");// why add 2?
			}
			if (videoMinBitrate != 0) {
				params.add(OPTION_VIDEO_MIN_BITRATE);
				params.add("" + videoMinBitrate + "k");
			}

			if (valid(videoSize)) {
				params.add(OPTION_VIDEO_SIZE);
				params.add("" + videoSize);
			}

			if (valid(videoAspect)) {
				params.add(OPTION_VIDEO_ASPECT);
				params.add("" + videoAspect);
			}

			if (samequality) {
				params.add(OPTION_VIDEO_SAME_QUALITY);
			}
		} else {
			params.add(OPTION_DISABLE_VIDEO);
		}

		if (qmin > 0) {
			params.add(OPTION_QMIN);
			params.add("" + qmin);
		}

		if (qmax > 0) {
			params.add(OPTION_QMAX);
			params.add("" + qmax);
		}

		if (mbd > 0) {
			params.add(OPTION_MBD);
			params.add("" + mbd);
		}

		if (flags != null && flags.length > 0) {
			params.add(OPTION_FLAGS);
			params.add(toOptionGroup(flags));
		}
		// bitrate torelance ,i don't know
		if (bt > 0) {
			params.add(OPTION_BT);
			params.add("" + bt + "k");
		}

		if (maxRate > 0) {
			params.add(OPTION_MAXRATE);
			params.add("" + maxRate + "k");
		}

		if (bufSize > 0) {
			params.add(OPTION_BUFSIZE);
			params.add("" + bufSize + "k");
		}
		if (level > 0) {
			params.add(OPTION_LEVEL);
			params.add("" + level);
		}
		if (coder > 0) {
			params.add(OPTION_CODER);
			params.add("" + coder);
		}

		if (vtag != null) {
			params.add(OPTION_VTAG);
			params.add("" + vtag);
		}

		if (deinterlace) {
			params.add(OPTION_DEINTERLACE);
		}

		if (loop) {
			params.add(OPTION_LOOP);
		}

		if (shortest) {
			params.add(OPTION_SHORTEST);
		}

		if (threads > 0) {
			params.add(OPTION_THREADS);
			params.add("" + threads);
		}

		if (mapList.size() > 0) {
			for (int i = 0; i < mapList.size(); i++) {
				params.add(OPTION_MAP);
				params.add("" + mapList.get(i));
			}
		}

		addLastOptions(inputs, params);
		params.add(output);

		return (String[]) params.toArray(new String[0]);
	}

	// dooverwrite
	public void addLastOptions(FFMpegInput[] inputs, List params) {

	}

	// dooverwrite
	public void addFirstOptions(List params) {

	}

	private String toOptionGroup(String[] arg) {
		String ret = "";
		for (int i = 0; i < arg.length; i++) {
			if (i != 0) {
				ret += "+";
			}
			ret += arg[i];
		}
		return ret;
	}

	private boolean valid(String param) {
		return param != null && param.length() > 0;
	}

	public int getAudioBitrate() {
		return audioBitrate;
	}

	public void setAudioBitrate(int audioBitrate) {
		this.audioBitrate = audioBitrate;
	}

	public int getAudioChannel() {
		return audioChannel;
	}

	public void setAudioChannel(int audioChannel) {
		this.audioChannel = audioChannel;
	}

	public String getAudioCodec() {
		return audioCodec;
	}

	public void setAudioCodec(String audioCodec) {
		this.audioCodec = audioCodec;
	}

	public int getAudioRate() {
		return audioRate;
	}

	public void setAudioRate(int audioRate) {
		this.audioRate = audioRate;
	}

	public String getFfmpegPath() {
		return ffmpegPath;
	}

	public void setFfmpegPath(String ffmpegPath) {
		this.ffmpegPath = ffmpegPath;
		if(new File(ffmpegPath).exists()){
			int g=FFMpegUtils.getFFMpegGeneration(ffmpegPath);
			setGeneration(g);
		}
		// set generation automatic
		//show log
		
		
	}

	public String getVideoAspect() {
		return videoAspect;
	}

	public void setVideoAspect(String videoAspect) {
		this.videoAspect = videoAspect;
	}

	public int getVideoBitrate() {
		return videoBitrate;
	}

	public void setVideoBitrate(int videoBitrate) {
		this.videoBitrate = videoBitrate;
	}

	public String getVideoCodec() {
		return videoCodec;
	}

	public void setVideoCodec(String videoCodec) {
		this.videoCodec = videoCodec;
	}

	public double getVideoRate() {
		return videoRate;
	}

	public void setVideoRate(double videoRate) {
		this.videoRate = videoRate;
	}

	public String getVideoSize() {
		return videoSize;
	}

	public void setVideoSize(String videoSize) {
		this.videoSize = videoSize;
	}

	public int getVideoMaxBitrate() {
		return videoMaxBitrate;
	}

	public void setVideoMaxBitrate(int videoMaxBitrate) {
		this.videoMaxBitrate = videoMaxBitrate;
	}

	public int getVideoMinBitrate() {
		return videoMinBitrate;
	}

	public void setVideoMinBitrate(int videoMinBitrate) {
		this.videoMinBitrate = videoMinBitrate;
	}

	public String getTarget() {
		return target;
	}

	public void setTarget(String target) {
		this.target = target;
	}

	public boolean isEnableAudio() {
		return enableAudio;
	}

	public void setEnableAudio(boolean enableAudio) {
		this.enableAudio = enableAudio;
	}

	public boolean isEnableVideo() {
		return enableVideo;
	}

	public void setEnableVideo(boolean enableVideo) {
		this.enableVideo = enableVideo;
	}

	
	public static int toKBytes(String duration, int bps) {
		if (bps == 0) {
			return 0;
		}

		int result = 0;
		if (duration != null) {
			Pattern durationPattern = Pattern
					.compile("(\\d+):(\\d+):(\\d+)\\.(\\d)+$");
			long millisecond = 0;
			Matcher match = durationPattern.matcher(duration);

			if (match.matches()) {
				int hour = Integer.parseInt(match.group(1));
				int minute = Integer.parseInt(match.group(2));
				int second = Integer.parseInt(match.group(3));
				int millis = Integer.parseInt(match.group(4));

				millisecond = hour * 60 * 60 * 1000;
				millisecond += minute * 60 * 1000;
				millisecond += second * 1000;
				millisecond += millis;
			} else {
				// invalid
				
				log.warn("invalid duration:" + duration);
				
			}
			
			if(millisecond<=0){
				log.warn("zero duration:" + duration +" maybe file is broken");
			}else{
			result = (int) (millisecond * bps / 8 / 1024);
			log.info(duration + "," + millisecond / 1000 + "," + bps
					+ "k" + " result=" + result);
			}
		}else{
			log.warn("duration==null");
		}
		return result;
	}

	/**
	 * @deprecated
	 */
	public boolean isHighQuality() {
		return highQuality;
	}

	/**
	 * @deprecated
	 */
	public void setHighQuality(boolean highQuality) {
		this.highQuality = highQuality;
	}

	public String getFormat() {
		return format;
	}

	public void setFormat(String format) {
		this.format = format;
	}

	public boolean isSamequality() {
		return samequality;
	}

	public void setSamequality(boolean samequality) {
		this.samequality = samequality;
	}

	public int getQmax() {
		return qmax;
	}

	public void setQmax(int qmax) {
		this.qmax = qmax;
	}

	public int getQmin() {
		return qmin;
	}

	public void setQmin(int qmin) {
		this.qmin = qmin;
	}

	public int getBt() {
		return bt;
	}

	public void setBt(int bt) {
		this.bt = bt;
	}

	public int getBufSize() {
		return bufSize;
	}

	public void setBufSize(int bufSize) {
		this.bufSize = bufSize;
	}

	public int getMaxRate() {
		return maxRate;
	}

	public void setMaxRate(int maxRate) {
		this.maxRate = maxRate;
	}

	public int getLevel() {
		return level;
	}

	public void setLevel(int level) {
		this.level = level;
	}

	public int getCoder() {
		return coder;
	}

	public void setCoder(int coder) {
		this.coder = coder;
	}

	public String getVtag() {
		return vtag;
	}

	public void setVtag(String vtag) {
		this.vtag = vtag;
	}

	public void setVideoSize(int videoWidth, int videoHeight) {
		setVideoSize(videoWidth + "x" + videoHeight);
	}

	public static String[] parseCommand(String line) {
		List list = new ArrayList();
		String values[] = line.split(" ");// simple parse;
		for (int i = 0; i < values.length; i++) {
			if (values[i].startsWith("\"") && values[i].endsWith("\"")) {
				String text = values[i].substring(1, values[i].length() - 1);
				if (!text.equals("")) {
					list.add(text);
				}
			} else {
				if (!values[i].equals("")) {
					list.add(values[i]);
				}
			}
		}
		return (String[]) list.toArray(new String[list.size()]);
	}

	public boolean isDeinterlace() {
		return deinterlace;
	}

	public void setDeinterlace(boolean deinterlace) {
		this.deinterlace = deinterlace;
	}

	public int getGeneration() {
		return generation;
	}

	public void setGeneration(int generation) {
		log.trace("generation:" + generation);
		this.generation = generation;
	}

	public void addMap(String map) {
		mapList.add(map);
	}
}
